/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal.configuration;

import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.sourceprojects.lycia.LyciaParser;
import org.sourceprojects.lycia.TagDefinitionParser;
import org.sourceprojects.lycia.annotations.Namespace;
import org.sourceprojects.lycia.annotations.Tag;
import org.sourceprojects.lycia.internal.Cache;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 
 * @author noctarius
 * @since 1.9.1
 */
public class XPathConfigurationUtils {
	private static final LyciaLoggerWrapper LOGGER = LyciaLoggerWrapper
			.getLogger(XPathConfigurationUtils.class);

	private XPathConfigurationUtils() {
	}

	public static final XPathExpression compileExpression(
			final String expression, final Namespace[] namespaces) {
		final XPath xpath = XPathFactory.newInstance().newXPath();
		xpath.setNamespaceContext(buildNamespaceContext(namespaces));

		try {
			return new XPathExpressionImpl(xpath.compile(expression));
		} catch (final XPathExpressionException e) {
			return new XPathExpressionImpl(null, xpath, expression);
		}
	}

	public static final boolean matches(final Element element,
			final XPathExpression compiled, final Cache<NodeList> cache,
			final LyciaParser<?> xmlParser) {
		try {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Trying to retrieve cached NodeList");
			}

			NodeList result = null;

			if (cache != null) {
				result = cache.get(String.valueOf(compiled.hashCode()));
			}

			if (result == null) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("No cached NodeList found, building it");
				}

				if (compiled instanceof XPathExpressionImpl) {
					final XPathExpressionImpl impl = (XPathExpressionImpl) compiled;
					impl.setDocument(element.getOwnerDocument());
				}

				result = (NodeList) compiled.evaluate(element,
						XPathConstants.NODESET);

				if (cache != null) {
					cache.put(String.valueOf(compiled.hashCode()), result,
							xmlParser.getCacheExpirationTime());
				}
			} else {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Cached NodeList found, using this one");
				}
			}

			if (result != null) {
				for (int i = 0; i < result.getLength(); i++) {
					final Node node = result.item(i);

					if (node == element) {
						return true;
					}
				}
			}

			return false;
		} catch (final XPathExpressionException e) {
			return false;
		}
	}

	public static final <B> boolean isHandleNamespace(
			final TagDefinitionParser<B> parser, final String namespaceURI) {
		for (final Tag tag : parser.getTagAnnotations()) {
			if (!TagConfigurationUtils.isNullXPathAnnotation(tag)) {
				if (tag.xpath().namespaces().length > 0) {
					for (final Namespace namespace : tag.xpath().namespaces()) {
						if (namespace.uri().equals(namespaceURI)) {
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	private static final NamespaceContext buildNamespaceContext(
			final Namespace[] namespaces) {
		if (namespaces == null) {
			return null;
		}

		final NamespaceContextImpl context = new NamespaceContextImpl();
		for (final Namespace namespace : namespaces) {
			if (namespace == null || namespace.prefix() == null
					|| namespace.uri() == null) {
				continue;
			}

			context.addNamespaceBinding(namespace.prefix(), namespace.uri());
		}

		return context;
	}
}
